#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <asm-generic/gpio.h>
#include <mach/fh_gpio_plat.h>

/*
 * GPIO Direction
 */
#define GPIO_DIR_INPUT          0
#define GPIO_DIR_OUTPUT         1

/*
 * GPIO Input status
 */
#define GPIO_INPUT_DISABLE      0
#define GPIO_INPUT_ENABLE       1

/*
 * GPIO Data mask
 */
#define GPIO_DATA_MASK          0
#define GPIO_DATA_UNMASK        1

/*
 * GPIO interrupt type
 */
#define GPIO_INT_TYPE_LEVEL     1
#define GPIO_INT_TYPE_EDGE      0

/*
 * GPIO interrupt polarity
 */
#define GPIO_INT_POL_LOW        0
#define GPIO_INT_POL_HIGH       1

#define GPIONUM_PER_CHIP        8

#define OFFSET_GPIO_DATA               (0x0000)	//get data or set data
#define OFFSET_GPIO_DMSK               (0x0004)	//mask when set data
#define OFFSET_GPIO_DIR                (0x0008)	//set direction
#define OFFSET_GPIO_IS                 (0x000C)	//interrupt sense(level/edge)
#define OFFSET_GPIO_IBE                (0x0010)	//interrupt both edges
#define OFFSET_GPIO_IEV                (0x0014)	//interrupt event high/low
#define OFFSET_GPIO_IE                 (0x0018)	//interrupt enable
#define OFFSET_GPIO_RIS                (0x001C) //raw interrupt status
#define OFFSET_GPIO_MIS                (0x0020)	//masked interrupt status
#define OFFSET_GPIO_IC                 (0x0024) //interrupt clear
#define OFFSET_GPIO_INEN               (0x0028)	//input enable
#define OFFSET_GPIO_DB_EN              (0x0030)	//debounce enable
#define OFFSET_GPIO_DB_BOTH            (0x0034)	//debounce both
#define OFFSET_GPIO_DB_INV             (0x0038) //debounce inverse

static inline void FH_GPIO_SetValue(void __iomem *base, int bit, int val)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_DATA);
	reg = val ? (reg | (1 << bit)) : (reg & ~(1 << bit));
	writel(reg, base + OFFSET_GPIO_DATA);
}

static inline int FH_GPIO_GetValue(void __iomem *base, int bit)
{
	return (readl(base + OFFSET_GPIO_DATA) >> bit) & 0x1;
}

static inline void FH_GPIO_SetDirection(void __iomem *base, int bit, int dir)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_DIR);
	reg = dir ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_DIR);
}

static inline void FH_GPIO_InputEnable(void __iomem *base, int bit, int en)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_INEN);
	reg = en ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_INEN);
}

static inline void FH_GPIO_SetDataMsk(void __iomem *base, int bit, int mask)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_DMSK);
	reg = mask ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_DMSK);
}

static inline int FH_GPIO_GetDirection(void __iomem *base, int bit)
{
	return (readl(base + OFFSET_GPIO_DIR) >> bit) & 0x1;
}

static inline void FH_GPIO_EnableDebounce(void __iomem *base, int bit, int bool)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_DB_EN);
	reg = bool ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_DB_EN);
}

static inline void FH_GPIO_SetInterruptType(void __iomem *base, int bit,
		int type)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IS);
	reg = type ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_IS);
}

static inline void FH_GPIO_SetInterruptPolarity(void __iomem *base, int bit,
		int pol)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IEV);
	reg = pol ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_IEV);
}

static inline void FH_GPIO_Set_BothEdgeInterrupt_enable(void __iomem *base, int bit)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IBE);
	reg |= (1 << bit);
	writel(reg, base + OFFSET_GPIO_IBE);
}

static inline void FH_GPIO_Set_BothEdgeInterrupt_disable(void __iomem *base,
	 int bit)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IBE);
	reg &= ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_IBE);
}

static inline void FH_GPIO_EnableInterrupt(void __iomem *base, int bit, int bool)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IE);
	reg = bool ? reg | (1 << bit) : reg & ~(1 << bit);
	writel(reg, base + OFFSET_GPIO_IE);
}

static inline unsigned int FH_GPIO_GetInterruptStatus(void __iomem *base)
{
	return readl(base + OFFSET_GPIO_MIS);
}

static inline void FH_GPIO_ClearInterrupt(void __iomem *base, int bit)
{
	unsigned int reg;

	reg = readl(base + OFFSET_GPIO_IC);
	reg |= (1 << bit);
	writel(reg, base + OFFSET_GPIO_IC);
}

static int _set_gpio_irq_type(void __iomem *base, unsigned int bit, unsigned int type)
{
	u32 int_type, int_polarity;

	switch (type & IRQF_TRIGGER_MASK) {
	case IRQ_TYPE_EDGE_BOTH:
		int_type = GPIO_INT_TYPE_EDGE;
		break;
	case IRQ_TYPE_EDGE_RISING:
		int_type = GPIO_INT_TYPE_EDGE;
		int_polarity = GPIO_INT_POL_HIGH;
		break;
	case IRQ_TYPE_EDGE_FALLING:
		int_type = GPIO_INT_TYPE_EDGE;
		int_polarity = GPIO_INT_POL_LOW;
		break;
	case IRQ_TYPE_LEVEL_HIGH:
		int_type = GPIO_INT_TYPE_LEVEL;
		int_polarity = GPIO_INT_POL_HIGH;
		break;
	case IRQ_TYPE_LEVEL_LOW:
		int_type = GPIO_INT_TYPE_LEVEL;
		int_polarity = GPIO_INT_POL_LOW;
		break;
	case IRQ_TYPE_NONE:
		return 0;
	default:
		return -EINVAL;
	}

	if ((type & IRQF_TRIGGER_MASK) == IRQ_TYPE_EDGE_BOTH) {
			FH_GPIO_SetInterruptType(base, bit, int_type);
			FH_GPIO_Set_BothEdgeInterrupt_enable(base, bit);
	}
	else {
		FH_GPIO_Set_BothEdgeInterrupt_disable(base, bit);
		FH_GPIO_SetInterruptType(base, bit, int_type);
		FH_GPIO_SetInterruptPolarity(base, bit, int_polarity);
	}

	return 0;
}

static inline void __iomem *irq_to_controller(struct irq_data *d)
{
	struct fh_gpio_chip *fh_gpio = irq_data_get_irq_chip_data(d);

	return fh_gpio->base;
}

static void gpio_irq_ack(struct irq_data *d)
{
	void __iomem *base;
	unsigned int gpio_bit = d->hwirq;

	base = irq_to_controller(d);
	FH_GPIO_ClearInterrupt(base, gpio_bit);
}

static void gpio_irq_enable(struct irq_data *d)
{
	void __iomem *base;
	unsigned gpio_bit = d->hwirq;

	base = irq_to_controller(d);
	FH_GPIO_EnableInterrupt(base, gpio_bit, 1);
}

static void gpio_irq_disable(struct irq_data *d)
{
	void __iomem *base;
	unsigned gpio_bit = d->hwirq;

	base = irq_to_controller(d);
	FH_GPIO_EnableInterrupt(base, gpio_bit, 0);
}

static int gpio_irq_type(struct irq_data *d, unsigned int type)
{
	void __iomem *base;
	unsigned int gpio_bit = d->hwirq;

	base = irq_to_controller(d);
	return _set_gpio_irq_type(base, gpio_bit, type);
}

static struct irq_chip gpio_irqchip = {
	.name           = "FH_GPIO_INTC",
	.irq_ack        = gpio_irq_ack,
	.irq_enable     = gpio_irq_enable,
	.irq_disable    = gpio_irq_disable,
	.irq_set_type   = gpio_irq_type,
};

static void gpio_irq_handler(struct irq_desc *desc)
{
	struct irq_data *irqdata = irq_desc_get_irq_data(desc);
	struct irq_chip *irqchip = irq_data_get_irq_chip(irqdata);
	struct fh_gpio_chip *fh_gpio = irq_desc_get_handler_data(desc);
	u32 irq_status;
	int gpio_num;

	chained_irq_enter(irqchip, desc);
	irq_status = FH_GPIO_GetInterruptStatus(fh_gpio->base);

	if (unlikely(irq_status == 0)) {
		pr_err("gpio irq status is zero.\n");
		return;
	}
	/* temporarily mask (level sensitive) parent IRQ */
	irqchip->irq_mask(irqdata);
	gpio_num = fls(irq_status) - 1;
	FH_GPIO_ClearInterrupt(fh_gpio->base, gpio_num);
	generic_handle_irq(irq_find_mapping(fh_gpio->irq_domain, gpio_num));
	irqchip->irq_unmask(irqdata);
	chained_irq_exit(irqchip, desc);
	/* now it may re-trigger */
}

/*
* This lock class tells lockdep that GPIO irqs are in a different
* category than their parents, so it won't report 0 recursion.
*/
static struct lock_class_key gpio_lock_class;

static int fh_gpio_irq_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hwirq)
{
	int ret;

	ret = irq_set_chip_data(irq, d->host_data);
	if (ret < 0)
		return ret;
	irq_set_lockdep_class(irq, &gpio_lock_class);
	irq_set_chip_and_handler(irq, &gpio_irqchip, handle_simple_irq);
	irq_set_noprobe(irq);

	return 0;

}

static void fh_gpio_irq_unmap(struct irq_domain *d, unsigned int irq)
{
	irq_set_chip_and_handler(irq, NULL, NULL);
	irq_set_chip_data(irq, NULL);
}

static struct irq_domain_ops fh_gpio_irq_ops = {
	.map = fh_gpio_irq_map,
	.unmap = fh_gpio_irq_unmap,
	.xlate = irq_domain_xlate_twocell,
};

static int fh_gpio_irq_create_domain(struct gpio_chip *c, unsigned offset)
{
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	return irq_create_mapping(chip->irq_domain, offset);
}

static int chip_to_irq(struct gpio_chip *c, unsigned offset)
{
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	if (offset > chip->chip.ngpio)
		return -ENXIO;
	return irq_find_mapping(chip->irq_domain, offset);
}

static int chip_gpio_get(struct gpio_chip *c, unsigned offset)
{
	u32 bit = offset % GPIONUM_PER_CHIP;
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	return FH_GPIO_GetValue(chip->base, bit);
}

static void chip_gpio_set(struct gpio_chip *c, unsigned int offset, int val)
{
	u32 bit = offset % GPIONUM_PER_CHIP;
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	FH_GPIO_SetDataMsk(chip->base, bit, GPIO_DATA_UNMASK);
	FH_GPIO_SetValue(chip->base, bit, val);
}

static int chip_direction_input(struct gpio_chip *c, unsigned offset)
{
	u32 bit = offset % GPIONUM_PER_CHIP;
	unsigned long flags;
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	spin_lock_irqsave(&chip->lock, flags);
	FH_GPIO_SetDirection(chip->base, bit, GPIO_DIR_INPUT);
	FH_GPIO_SetDataMsk(chip->base, bit, GPIO_DATA_UNMASK);
	FH_GPIO_InputEnable(chip->base, bit, GPIO_INPUT_ENABLE);
	spin_unlock_irqrestore(&chip->lock, flags);

	return 0;
}

static int chip_direction_output(struct gpio_chip *c, unsigned offset, int val)
{
	u32 bit = offset % GPIONUM_PER_CHIP;
	unsigned long flags;
	struct fh_gpio_chip *chip;

	chip = container_of(c, struct fh_gpio_chip, chip);
	spin_lock_irqsave(&chip->lock, flags);
	FH_GPIO_SetDirection(chip->base, bit, GPIO_DIR_OUTPUT);
	FH_GPIO_SetDataMsk(chip->base, bit, GPIO_DATA_UNMASK);
	FH_GPIO_SetValue(chip->base, bit, val);
	spin_unlock_irqrestore(&chip->lock, flags);

	return 0;
}

static int chip_gpio_set_debounce(struct gpio_chip *c, unsigned offset,
	unsigned int debounce)
{
	u32 bit = offset % GPIONUM_PER_CHIP;
	unsigned long flags;
	char db_clk_name[16] = {0};
	struct clk *gpio_clk = NULL;
	int ret = 0;
	struct fh_gpio_chip *chip;
	bool enabled = !!debounce;
	unsigned int clk_rate = 0;

	sprintf(db_clk_name, "gpio%d_gbclk", (offset / GPIONUM_PER_CHIP));
	gpio_clk = clk_get(NULL, db_clk_name);
	if (IS_ERR(gpio_clk))
		return PTR_ERR(gpio_clk);

	clk_rate = 1000000UL / debounce;

	ret = clk_set_rate(gpio_clk, clk_rate);
	if (ret) {
		pr_err("Set GPIO Debounce Clk fail\n");
		return ret;
	}

	ret = clk_prepare_enable(gpio_clk);
	if (ret) {
		pr_err("Set GPIO Debounce Clk fail\n");
		return ret;
	}

	chip = container_of(c, struct fh_gpio_chip, chip);
	spin_lock_irqsave(&chip->lock, flags);
	FH_GPIO_EnableDebounce(chip->base, bit, enabled);
	spin_unlock_irqrestore(&chip->lock, flags);

	return 0;
}

static void fh_gpio_irq_init(struct platform_device *pdev)
{
	int i, gpio_irq;
	struct fh_gpio_chip *plat_data;

	plat_data = pdev->dev.platform_data;

	for (i = 0; i < GPIONUM_PER_CHIP; i++) {
		gpio_irq = fh_gpio_irq_create_domain(&plat_data->chip, i);
		irq_set_lockdep_class(gpio_irq, &gpio_lock_class);
		irq_set_chip_and_handler(gpio_irq, &gpio_irqchip, handle_simple_irq);
		irq_set_chip_data(gpio_irq, plat_data);
	}

	irq_set_chained_handler_and_data(plat_data->irq, gpio_irq_handler, plat_data);
}

static struct of_device_id const fh_gpio_of_match[] = {
	{ .compatible = "fh,fh-gpio" },
	{}
};
MODULE_DEVICE_TABLE(of, fh_gpio_of_match);

static int fh_gpio_probe(struct platform_device *pdev)
{
	int err = -EIO;
	int id;
	struct fh_gpio_chip *plat_data;
#ifdef CONFIG_OF
	int ngpio;
	const struct of_device_id *match;
	struct device_node *np = pdev->dev.of_node;
#else
	struct resource *res;
#endif

#ifdef CONFIG_OF
	match = of_match_device(fh_gpio_of_match, &pdev->dev);
	if (!match) {
		pr_info("Failed to find gpio controller\n");
		return -ENODEV;
	}

	plat_data = devm_kzalloc(&pdev->dev, sizeof(*plat_data), GFP_KERNEL);
	if (!plat_data)
		return -ENOMEM;

	plat_data->chip.of_node = np;
	/* set up the driver-specific struct */
	of_property_read_u32(np, "ngpio", &ngpio);
	plat_data->chip.ngpio = ngpio;
	of_property_read_u32(np, "base", &plat_data->chip.base);
	of_property_read_u32(np, "id", &id);

	plat_data->irq = irq_of_parse_and_map(np, 0);
	if (plat_data->irq < 0) {
		dev_err(&pdev->dev, "gpio interrupt is not available.\n");
		return plat_data->irq;
	}

	plat_data->base = of_iomap(np, 0);
	if (plat_data->base == NULL) {
		err = -ENXIO;
		goto release_irq;
	}

	plat_data->irq_domain = irq_domain_add_linear(np, plat_data->chip.ngpio, &fh_gpio_irq_ops, plat_data);

	if (!plat_data->irq_domain) {
		dev_err(&pdev->dev, "Couldn't allocate IRQ domain\n");
		err = -ENXIO;
		goto release_io;
	}
#else
	plat_data = pdev->dev.platform_data;

	id = pdev->id;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "can't fetch device resource info\n");
		return err;
	}

	plat_data->irq  = irq_create_mapping(NULL, platform_get_irq(pdev, 0));
	if (plat_data->irq < 0) {
		dev_err(&pdev->dev, "gpio interrupt is not available.\n");
		return plat_data->irq;
	}

	plat_data->base = ioremap(res->start, resource_size(res));
	if (plat_data->base == NULL) {
		err = -ENXIO;
		goto release_irq;
	}

	plat_data->irq_domain = irq_domain_add_linear(NULL,
		plat_data->chip.ngpio, &fh_gpio_irq_ops, plat_data);

	if (!plat_data->irq_domain) {
		dev_err(&pdev->dev, "Couldn't allocate IRQ domain\n");
		err = -ENXIO;
		goto release_io;
	}
#endif

	plat_data->chip.direction_input = chip_direction_input;
	plat_data->chip.direction_output = chip_direction_output;
	plat_data->chip.get = chip_gpio_get;
	plat_data->chip.set = chip_gpio_set;
	plat_data->chip.to_irq = chip_to_irq;
	plat_data->chip.set_debounce = chip_gpio_set_debounce;

	plat_data->pdev = pdev;
	spin_lock_init(&plat_data->lock);
	/* finally, register with the generic GPIO API */
	err = gpiochip_add(&plat_data->chip);
	if (err) {
		pr_err("GPIO support load fail.\n");
		goto release_domain;
	}

	fh_gpio_irq_init(pdev);
	pr_debug("GPIO support successfully loaded.\n\tBase Addr: 0x%p\n",
		plat_data->base);

	return 0;

release_domain:
	irq_domain_remove(plat_data->irq_domain);
release_io:
	iounmap(plat_data->base);
release_irq:
	irq_dispose_mapping(plat_data->irq);
	free_irq(plat_data->irq, plat_data);

	return err;
}

static int fh_gpio_remove(struct platform_device *pdev)
{
	struct fh_gpio_chip *plat_data;

	plat_data = pdev->dev.platform_data;

	gpiochip_remove(&plat_data->chip);
	irq_domain_remove(plat_data->irq_domain);
	iounmap(plat_data->base);

	return 0;
}

static struct platform_driver fh_gpio_driver = {
	.driver = {
		.name = GPIO_NAME,
		.owner = THIS_MODULE,
		.of_match_table = fh_gpio_of_match,
	},
	.probe = fh_gpio_probe,
	.remove = fh_gpio_remove,
};

static int __init fh_gpio_init(void)
{
	return platform_driver_register(&fh_gpio_driver);
}

static void __exit fh_gpio_exit(void)
{
	platform_driver_unregister(&fh_gpio_driver);
}

module_init(fh_gpio_init);
module_exit(fh_gpio_exit);

MODULE_AUTHOR("QIN");
MODULE_DESCRIPTION("MOL GPIO V1 device driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform: FH");
